﻿using FantasticMusicPlayer.dbo.Model;
using FantasticMusicPlayer.lib;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
using System.IO;
using System.Linq;
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using static FantasticMusicPlayer.lib.GdiSystem;

namespace FantasticMusicPlayer
{
    public partial class Form1 : Form
    {
        readonly BassPlayerImpl player = new BassPlayerImpl();
        PlayerController controller;

        Icon notification_play, notification_pause, notification_next;

        public Form1()
        {
            InitializeComponent();
            notification_play = IconMaker.toIcon(Properties.Resources.notify_icon_play);
            notification_pause = IconMaker.toIcon(Properties.Resources.notify_icon_pause);
            notification_next = IconMaker.toIcon(Properties.Resources.notify_icon_next);
            icoPlayPause.Icon = notification_play;
            icoNext.Icon = notification_next;
            icoPlayPause.MouseUp += IcoPlayPause_Click;
            icoNext.MouseUp += IcoNext_MouseUp;
        }

        private void IcoNext_MouseUp(object sender, MouseEventArgs e)
        {
            if(e.Button == MouseButtons.Left)
            {
                btnNext_Click(null,null);
            }
            else
            {
                btnPrev_Click(null, null);
            }
        }

        private void IcoPlayPause_Click(object sender, MouseEventArgs e)
        {
            if(e.Button == MouseButtons.Left)
            {
                player?.PlayOrPause();

            }
        }

        private void Invoke(Action p)
        {
            Invoke((Delegate)p);
        }

        private void Controller_SongChanged(object sender, SongSwitchedEventArgs e)
        {
            player.Looping = controller.LoopMode == 1;
            player.TargetLUFS = controller.CurrentList.GetBalancedLUFS();
            player.Load(e.CurrentSong);
            player.Play();
        }

        private void Player_SongInfoAvailable(object sender, SongInfoEventArgs e)
        {
            runOnUiThread(() =>
            {
                lblTitle.Text = e.title;
                lblArtsit.Text = (e.artist + "    " + e.album).Trim();
                Text = e.title;
                var trayIconText = e.title + " - " + Application.ProductName;
                if(trayIconText.Length > 60)
                {
                    trayIconText = trayIconText.Substring(0, 60 - (" - " + Application.ProductName).Length) + " - " + Application.ProductName;
                }
                icoPlayPause.Text = trayIconText;
                imgHiResAudio.Enabled = e.HiResAudio;
                this.lyricManager = e.LyricManager;
            });
            checkFavStatus();
            updateTopControl();


        }

        private void updateTopControl()
        {
            crossfadeCountdown = 0.01f;
        }

        void runOnUiThread(Action a)
        {
            Invoke(a);
        }

        readonly GaussianBlur blurer = new GaussianBlur(48);

        public Bitmap copyForGuassianBlur(Image img)
        {
            Bitmap bmp = new Bitmap(100, 100);
            using (Graphics g = Graphics.FromImage(bmp))
            {
                g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
                g.DrawImage(img, 0, 0, 100, 100);
            }
            return bmp;
        }

        private void Player_CoverAvailable(object sender, AlbumEventArgs e)
        {
            Bitmap bmp = new Bitmap(locMask.Width+1, locMask.Height+1);
            using (Graphics g = Graphics.FromImage(bmp))
            {
                g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
                g.CompositingQuality = CompositingQuality.HighQuality;
                g.DrawImage(e.cover, 0, 0, locMask.Width+1, locMask.Height+1);

            }
            oldBackground = currentBackground;
            // updateBackground();
            if (!e.Fallback)
            {
                currentBackground = new Bitmap(blurer.ProcessImage(copyForGuassianBlur(e.cover)), this.Width + 20, this.Height + 20);

            }
            else
            {
                currentBackground = null;
            }

            crossfadeCountdown = 1;
            e.cover.Dispose();
            oldCover = currentCover;
            currentCover = bmp;

            using(Bitmap roundTrayIcon = new Bitmap(currentCover))
            {
                using (Graphics g = Graphics.FromImage(roundTrayIcon))
                {
                    g.Clear(Color.Transparent);
                    GraphicsPath path = new GraphicsPath();
                    path.AddEllipse(0, 0, locMask.Width + 1, locMask.Height + 1);
                    g.SetClip(path);
                    g.DrawImage(currentCover, 0, 0, locMask.Width + 1, locMask.Height + 1);
                    path.Dispose();

                }
                runOnUiThread(() =>
                {
                    this.Icon = IconMaker.toIcon(roundTrayIcon);
                });
            }
           
            lastDiscAngel = discangel;
            discangel = 0;
            GC.Collect();
        }

        Bitmap oldCover = null;

        private void Player_Stopped(object sender, EventArgs e)
        {
            runOnUiThread(() => controller.onSongStop());

        }

        Bitmap currentCover = cropToCircle(new Bitmap(Properties.Resources.default_cover, new Size(228, 228)));


        static Bitmap cropToCircle(Bitmap bmp)
        {
            using (Graphics g = Graphics.FromImage(bmp))
            {
                g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
                g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
                float r = bmp.Width * (float)((Math.Sqrt(2) - 1)) / 1.4142136f;

                float r2 = 34.5f;

                Pen p = new Pen(Brushes.Transparent, r);
                g.DrawEllipse(p, 0 - r2, 0 - r2, bmp.Width + r2 + r2 - 1, bmp.Height + r2 + r2 - 1);
            }
            return bmp;
        }

        private void Form1_Disposed(object sender, EventArgs e)
        {
            player.Dispose();
        }

        protected override CreateParams CreateParams
        {

            get
            {
                var orignalParam = base.CreateParams;
                orignalParam.ExStyle |= Win32.WS_EX_LAYERED;
                return orignalParam;
            }
        }
        internal GdiSystem gdi;
        public Size shadowedWindowSize = new Size(800, 470);
        public Size orignalWindowSize = new Size(768, 440);
        public Point shadowOffset = new Point(16, 8);
        private void Form1_Load(object sender, EventArgs e)
        {
            this.Icon = Properties.Resources.icon;
            this.Size = shadowedWindowSize;

            gdi = new GdiSystem(this);

            gdi.Graphics.DrawImage(Properties.Resources.startup, locGlowing.Left + shadowOffset.X, locGlowing.Top + shadowOffset.Y, locGlowing.Width, locGlowing.Height);

            gdi.UpdateWindow();

            gdi.Graphics.TextRenderingHint = TextRenderingHint.AntiAlias;
            gdi.Graphics.CompositingQuality = CompositingQuality.HighSpeed;
            gdi.Graphics.SmoothingMode = SmoothingMode.HighQuality;

            resizeBitmap();
            player.init();
            Disposed += Form1_Disposed;
            player.Stopped += Player_Stopped;
            player.PlaybackStateChanged += Player_PlaybackStateChanged;
            player.CoverAvailable += Player_CoverAvailable;
            player.SongInfoAvailable += Player_SongInfoAvailable;
            controller = new PlayerController(new CurrentDirectorySongProvider());
            controller.SongChanged += Controller_SongChanged;

            initLayers();
            layersInited();
            renderTimer.Start();
            if (controller.CurrentPlaying != null)
            {
                controller.ImReady();
                player.Pause();
                controller.LoopMode = Properties.Settings.Default.playmode;
                controller.Shuffe = Properties.Settings.Default.shuffemode;
                player.BassBoost = Properties.Settings.Default.bassboost;
                int dspType = Properties.Settings.Default.dsptype;

                this.dsptype = dspType % 10;
                player.DynamicRangeCompressed = dspType / 10 % 10;
                if (dspType == 1)
                {
                    ((DspSwitcher)player.SurroundSound).WrappedDSP = new SurroundDSP();
                }
                if (dspType == 2)
                {
                    ((DspSwitcher)player.SurroundSound).WrappedDSP = new SpeakerInRoomDSP();
                }

                int spectrumMode = Properties.Settings.Default.spectrummode;

                SpectrumMode = spectrumMode % 100;
                _showDesktopLyrics = ((spectrumMode / 100) % 10) > 0;

                ShowTrayIcons = ((spectrumMode / 1000) % 10) > 0;

                try
                {
                    player.LoadFx(Properties.Settings.Default.fxfile);
                }
                catch (Exception)
                {
                }
            }
            RegisterHotKey(this.Handle, 2, 0, Keys.MediaPreviousTrack);
            RegisterHotKey(this.Handle, 3, 0, Keys.MediaPlayPause);
            RegisterHotKey(this.Handle, 4, 0, Keys.MediaNextTrack);
            initLyrics();
        }

        private void Player_PlaybackStateChanged(object sender, EventArgs e)
        {
            try
            {
                icoPlayPause.Icon = player.IsPlaying ? notification_pause : notification_play;
            }
            catch { }
        }

        private bool _showDesktopLyrics = false;

        private bool _showTryIcons = false;
        public bool ShowTrayIcons { 
            get {
                return _showTryIcons;
            } 
            private set { 
                _showTryIcons = value;
                icoPlayPause.Visible = _showTryIcons;
                icoNext.Visible = _showTryIcons;
            } 
        }

        private bool ShowDesktopLyrics
        {
            get
            {
                return _showDesktopLyrics;
            }
            set
            {
                _showDesktopLyrics = value;
                if (lyricLayer != null)
                {
                    lyricLayer.Visible = value;
                }
            }
        }

        void resizeBitmap()
        {
            frame = new Bitmap(frame, this.orignalWindowSize);
            defaultBackground = new Bitmap(defaultBackground, this.orignalWindowSize);
            glowBall = new Bitmap(glowBall, locButtonBlur.Size);
            progressThumb = new Bitmap(progressThumb, locButtonBlur.Size);
            ic_btnPause = new Bitmap(ic_btnPause, btnPlay.Size);
            ic_btnPlay = new Bitmap(ic_btnPlay, btnPlay.Size);
            coverGlowing = new Bitmap(coverGlowing, locGlowing.Size);
            shadowImage = new Bitmap(shadowImage, shadowedWindowSize);
        }
        Bitmap shadowImage = Properties.Resources.bg_layer;
        Bitmap frame = new Bitmap(Properties.Resources.window_frame);
        Bitmap defaultBackground = new Bitmap(Properties.Resources.default_background);
        Bitmap glowBall = new Bitmap(Properties.Resources.glow_ball);
        Bitmap currentBackground = null;
        Bitmap coverGlowing = new Bitmap(Properties.Resources.bg_glowing);




        GraphicsLayer backgroundLayer, gfxLayer, bottomControlLayer, spectrumLayer, discDisplayLayer;
        GraphicsLayer siderBackgroundLayer, siderForegroundLayer;
        GraphicsLayer listLayer;
        GraphicsLayer lyricLayer;

        internal List<GraphicsLayer> layers = new List<GraphicsLayer>();

        public void initLayers()
        {
            backgroundLayer = new GraphicsLayer(this, new Control() { Location = new Point(0, 0), Size = this.orignalWindowSize }, false) { Text = "背景层" };
            spectrumLayer = new GraphicsLayer(this, locSpectrumArea, false) { Text = "频谱显示层",selfCompose = false,selfComposeAction = updateSpectrum };
            siderBackgroundLayer = new GraphicsLayer(this, tblVolumn, false) { Text = "右控制器背景" };
            gfxLayer = new GraphicsLayer(this, new Control() { Location = Point.Empty, Size = this.orignalWindowSize }, false) { Text = "控件特效层", selfCompose = true, selfComposeAction = computeSfx };
            siderForegroundLayer = new GraphicsLayer(this, tblVolumn, false) { Text = "右控制器前景" };

            var discDisplayBackLayer = new GraphicsLayer(this, locGlowing, false) { Text = "唱片发光特效层", selfCompose = true, selfComposeAction = updateSpinningDisc };
            discDisplayLayer = new GraphicsLayer(this, locMask, false) { Text = "唱片层", selfCompose = true, selfComposeAction = new Action(() => { }) };
            locLyric.Width = 1280;
            lyricLayer = new LyricGraphicsLayer(this, locLyric) { Text = "歌词层" };
            layers.Remove(lyricLayer);
            listLayer = new GraphicsLayer(this, tblList, false) { Text = "列表层" };

            bottomControlLayer = new GraphicsLayer(this, tblBottomControl, false) { Text = "底部控制器", selfCompose = true, selfComposeAction = updateBottomControl };

            GraphicsLayer lastForm = null;

            for (int i = 0; i < layers.Count; i++)
            {
                if (layers[i].isActualWindow)
                {
                    if (lastForm == null)
                    {

                        layers[i].Show(this);
                    }
                    else
                    {

                        layers[i].Show(lastForm._this);
                    }
                    lastForm = layers[i];
                }
            }
            layers.Add(lyricLayer);
            lyricLayer._this.Show();
            lyricLayer._this.TopMost = true;
        }

        public void layersInited()
        {
            oldBackground = new Bitmap(defaultBackground);
            DrawUtils.drawAlphaImage(backgroundLayer.g, oldBackground, 0, 0, this.orignalWindowSize.Width + 20, this.orignalWindowSize.Height + 20, 1);
            crossfadeCountdown = 1;
            bottomControlLayer.g.Clear(Color.Transparent);
            spectrumLayer.g.Clear(Color.Transparent);
            spectrumLayer.g.CompositingQuality = CompositingQuality.HighSpeed;

            

            discDisplayLayer.g.Clear(Color.Transparent);
            discDisplayLayer.g.DrawImage(currentCover, 0, 0, discDisplayLayer.relativeRect.Width, discDisplayLayer.relativeRect.Height);


            listLayer.g.Clear(Color.Transparent);
            lyricLayer.g.Clear(Color.Transparent);
            updateAll();
            updateAll2();

            foreach (Control control in this.Controls)
            {
                control.Left += shadowOffset.X;
                control.Top += shadowOffset.Y;
            }


        }

        Bitmap oldBackground = null;
        bool updateBackgroudFrameSkip = false;
        void updateBackground()
        {
            updateBackgroudFrameSkip = !updateBackgroudFrameSkip;
            if (updateBackgroudFrameSkip) { return; }
            crossfadeCountdown -= 0.098f;
            //backgroundLayer.g.CompositingQuality = CompositingQuality.HighSpeed;
            if (crossfadeCountdown > 0.05)
            {
                backgroundLayer.g.Clear(Color.Transparent);
                
                try
                {
                    if (oldBackground != null)
                    {
                        backgroundLayer.g.DrawImage(oldBackground, 0, 0, this.orignalWindowSize.Width + 20, this.orignalWindowSize.Height + 20);
                    }
                    DrawUtils.drawAlphaImage(backgroundLayer.g, currentBackground ?? defaultBackground, 0, 0, this.orignalWindowSize.Width + 20, this.orignalWindowSize.Height + 20, 1 - crossfadeCountdown);

                    postUpdateBackground();
                }
                catch {
#if DEBUG
                    Console.WriteLine("Audio Switch Crash Trigged");
#endif
                    
                }
            }
            else
            {
                backgroundLayer.g.Clear(Color.Transparent);
                try
                {

                    backgroundLayer.g.DrawImage(currentBackground ?? defaultBackground, 0, 0, this.orignalWindowSize.Width + 20, this.orignalWindowSize.Height + 20);

                    postUpdateBackground();
                }
                catch
                {
#if DEBUG
                    Console.WriteLine("Audio Switch Crash Trigged");
#endif

                }
                crossfadeCountdown = 0;
            }

            //backgroundLayer.g.CompositingQuality = CompositingQuality.HighQuality;
            backgroundLayer.UpdateWindow();
        }

        void postUpdateBackground()
        {
            Graphics g = backgroundLayer.g;
            g.DrawImage(frame, Point.Empty);
            Point titleLocation = lblTitle.Location;
            Point artistLocation = lblArtsit.Location;
            Point hiresIconLocation = imgHiResAudio.Location;
            Point favButtonLocation = imgBass.Location;
            titleLocation.Offset(tblTopInfo.Left - shadowOffset.X, tblTopInfo.Top - shadowOffset.Y);
            artistLocation.Offset(tblTopInfo.Left - shadowOffset.X, tblTopInfo.Top - shadowOffset.Y);
            hiresIconLocation.Offset(tblTopInfo.Left - shadowOffset.X, tblTopInfo.Top - shadowOffset.Y);
            favButtonLocation.Offset(tblTopInfo.Left - shadowOffset.X, tblTopInfo.Top - shadowOffset.Y);

            g.DrawString(lblTitle.Text, lblTitle.Font, white, new Rectangle(titleLocation, lblTitle.Size), alignLeft);
            g.DrawString(lblArtsit.Text, lblArtsit.Font, white, new Rectangle(artistLocation, lblArtsit.Size), alignLeft);
            if (imgHiResAudio.Enabled)
            {
                g.DrawImage(imgHiResAudio.Image, new Rectangle(hiresIconLocation, imgHiResAudio.Size));
            }

            g.DrawImage(isFavChecked ? imgFavOn : imgFavOff, new Rectangle(favButtonLocation, imgBass.Size));

        }

        public void updateAll() => layers.ForEach(l => l.UpdateWindow());
        public void updateAll2() => layers.ForEach(l => l.updatePosition());


        [DllImport("user32.dll")]
        public static extern bool ReleaseCapture();
        [DllImport("user32.dll")]
        public static extern bool SendMessage(IntPtr hwnd, int wMsg, int wParam, int lParam);
        public const int WM_SYSCOMMAND = 0x0112;
        public const int SC_MOVE = 0xF010;
        public const int HTCAPTION = 0x0002;

        int cx, cy;
        bool triggledMove = false;
        bool isMouseDown = false;
        private void Form1_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                cx = e.X;
                cy = e.Y;
                triggledMove = false;
                isMouseDown = true;
            }

        }

        private void Form1_MouseMove(object sender, MouseEventArgs e)
        {
            if (isMouseDown && !triggledMove)
            {
                if (Math.Abs(cx - e.X) + Math.Abs(cy - e.Y) > 10)
                {
                    triggledMove = true;
                    ReleaseCapture();
                    SendMessage(this.Handle, WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);
                }
            }
        }

        private void Form1_MouseUp(object sender, MouseEventArgs e)
        {
            isMouseDown = false;
            if (!triggledMove)
            {
                hideList(new Action(() => { }));
            }
        }

        private void btnMin_Click(object sender, EventArgs e)
        {
            WindowState = FormWindowState.Minimized;
        }
        bool isShowing = false;
        private void Form1_SizeChanged(object sender, EventArgs e)
        {
            layers.Where(l => l != lyricLayer).ToList().ForEach(l => l.Visible = this.WindowState != FormWindowState.Minimized);
            isShowing = this.WindowState != FormWindowState.Minimized;
        }



        private void btnClose_Click(object sender, EventArgs e)
        {
            Close();
        }


        private void sfxAttaker(object sender, EventArgs e)
        {
            Control ctl = (Control)sender;
            Point loc = PointToClient(ctl.PointToScreen(Point.Empty));
            lightTargetX = loc.X + ctl.Width / 2 - shadowOffset.X;
            lightTargetY = loc.Y + ctl.Height / 2 - shadowOffset.Y;
            if (alpha == 0)
            {
                lightMovementX = lightTargetX;
                lightMovementY = lightTargetY;
            }
            mouseInAnyControl = true;
            gfxLayer.shouldCompose = true;
        }


        private void sfxReleaser(object sender, EventArgs e)
        {
            mouseInAnyControl = false;
        }

        bool mouseInAnyControl = false;
        float lightLumianceOverlay = 0.0f;

        bool emphasis = false;

        private void sfxEmphasis(object sender, MouseEventArgs e)
        {
            emphasis = true;
        }

        private void sfxDeemphasis(object sender, MouseEventArgs e)
        {
            emphasis = false;
        }

        float emphasisStrength = 0f;

        float alpha = 0;
        float lightMovementX = 0;

        readonly StringFormat alignLeft = new StringFormat() { LineAlignment = StringAlignment.Center, Alignment = StringAlignment.Near, FormatFlags = StringFormatFlags.NoWrap };
        readonly StringFormat alignRight = new StringFormat() { LineAlignment = StringAlignment.Center, Alignment = StringAlignment.Far, FormatFlags = StringFormatFlags.NoWrap };


        String timeToString(long ms)
        {
            long minutes = ms / 60000;
            long second = (ms % 60000) / 1000;
            return String.Format("{0:D2}:{1:D2}", minutes, second);
        }

        float lightMovementY = 0;
        float lightTargetX = 0;
        float lightTargetY = 0;

        private void btnPlay_Click(object sender, EventArgs e)
        {
            player.PlayOrPause();
        }

        readonly float movementSmoothFactor = 0.17f;



        bool progressChanging = false;

        float crossfadeCountdown = 0;

        private void lblProgressManager_MouseDown(object sender, MouseEventArgs e)
        {
            progressChanging = true;
        }

        private void lblProgressManager_MouseMove(object sender, MouseEventArgs e)
        {
            if (progressChanging)
            {
                int targetvalue = (int)((float)numProgress.Maximum * e.X / lblProgressManager.Width);
                if (targetvalue > numProgress.Maximum) { targetvalue = numProgress.Value; }
                if (targetvalue < 0) { targetvalue = 0; }
                player.CurrentPosition = targetvalue;
                numProgress.Value = targetvalue;
            }
        }


        private void lblProgressManager_MouseUp(object sender, MouseEventArgs e)
        {
            if (progressChanging)
            {
                int targetvalue = (int)((float)numProgress.Maximum * e.X / lblProgressManager.Width);
                if (targetvalue > numProgress.Maximum) { targetvalue = numProgress.Value; }
                if (targetvalue < 0) { targetvalue = 0; }
                player.CurrentPosition = targetvalue;
                numProgress.Value = targetvalue;
            }
            progressChanging = false;
        }

        public void computeSfx()
        {
            if (mouseInAnyControl)
            {
                alpha = Math.Min(3, alpha + 0.05f);
            }
            else
            {
                alpha = Math.Max(0, alpha - 0.009f);
            }
            if (emphasis)
            {
                emphasisStrength = Math.Min(0.3f, emphasisStrength + 0.05f);
            }
            else
            {
                emphasisStrength = Math.Max(0, emphasisStrength - 0.05f);
            }

            float finalAlpha = Math.Min(1, alpha);
            if (lightLumianceOverlay < 0.5) { finalAlpha -= lightLumianceOverlay; }
            if (lightLumianceOverlay >= 0.5) { finalAlpha -= (1 - lightLumianceOverlay); }

            finalAlpha = finalAlpha * 0.7f + emphasisStrength;

            float dx = (lightTargetX - lightMovementX) * movementSmoothFactor;
            float dy = (lightTargetY - lightMovementY) * movementSmoothFactor;
            lightMovementX += dx;
            lightMovementY += dy;

            float movementMultiplier = 1f / (1f + (float)Math.Sqrt(dx * dx + dy * dy));

            Graphics g = gdi.Graphics;
            DrawUtils.drawAlphaImage(g, glowBall, lightMovementX - locButtonBlur.Width / 2 + shadowOffset.X, lightMovementY - locButtonBlur.Height / 2 + shadowOffset.Y, locButtonBlur.Width, locButtonBlur.Height, finalAlpha * movementMultiplier);
            gfxLayer.shouldCompose = alpha >= 0.01f;
        }

        readonly Brush white = Brushes.White;

        Bitmap progressThumb = new Bitmap(Properties.Resources.glowing_drag_thumb);
        Bitmap ic_btnPlay = new Bitmap(Properties.Resources.ic_play);



        Bitmap ic_btnPause = new Bitmap(Properties.Resources.ic_pause);
        void updateBottomControl()
        {
            numProgress.Maximum = (int)player.TotalPosition;

            numProgress.Value = (int)(player.CurrentPosition >= numProgress.Maximum ? numProgress.Maximum : player.CurrentPosition);


            Graphics g = gdi.Graphics;
            g.SetClip(new Rectangle(shadowOffset, orignalWindowSize));
            g.TranslateTransform(bottomControlLayer.relativeRect.Left + shadowOffset.X, bottomControlLayer.relativeRect.Top+shadowOffset.Y);
            g.DrawImage(player.IsPlaying ? ic_btnPause : ic_btnPlay, btnPlay.Location);
            g.DrawString(timeToString(player.CurrentPosition), lblCurrentTime.Font, white, new Rectangle(lblCurrentTime.Location, lblCurrentTime.Size), alignRight);
            g.DrawString(timeToString(player.TotalPosition), lblTotalTime.Font, white, new Rectangle(lblTotalTime.Location, lblTotalTime.Size), alignLeft);

            //g.DrawImage(progressThumb, (float)this.orignalWindowSize.Width * numProgress.Value / numProgress.Maximum - locButtonBlur.Width / 2, locProgress.Top - locButtonBlur.Height / 2);
            g.FillEllipse(white, (float)this.orignalWindowSize.Width * numProgress.Value / numProgress.Maximum - 4, locProgress.Top - 4, 8, 8);
            float finalAlpha = 1;
            if (lightLumianceOverlay < 0.5) { finalAlpha -= lightLumianceOverlay; }
            if (lightLumianceOverlay >= 0.5) { finalAlpha -= (1 - lightLumianceOverlay); }

            DrawUtils.drawAlphaImage(g, progressThumb, (float)this.orignalWindowSize.Width * numProgress.Value / numProgress.Maximum - locButtonBlur.Width / 2, locProgress.Top - locButtonBlur.Height / 2, locButtonBlur.Width, locButtonBlur.Height, finalAlpha);
            g.ResetTransform(); 
            g.ResetClip();
        }

        private void btnVolume_Click(object sender, EventArgs e)
        {
            if (!tblVolumn.Visible)
            {
                tblVolumn.Visible = true;
                tblUtils.Visible = false;
                sliderAnimationFadein = true;
                sliderAnimationCountdown = 1;
            }
            else
            {
                sliderAnimationFadein = false;
                sliderAnimationCountdown = 1;
            }
        }

        private void btnMore_Click(object sender, EventArgs e)
        {
            if (!tblUtils.Visible)
            {
                tblUtils.Visible = true;
                tblVolumn.Visible = false;
                sliderAnimationFadein = true;
                sliderAnimationCountdown = 1;
            }
            else
            {
                sliderAnimationFadein = false;
                sliderAnimationCountdown = 1;
            }
        }


        private int _spectrumMode = 1;
        int SpectrumMode
        {
            get
            {
                return _spectrumMode;
            }
            set
            {
                _spectrumMode = value;
                if (value == 1 || value == 3)
                {
                    spectrumLayer.changePosition(locSpectrumArea.Location);
                }
                if (value == 2 || value == 4)
                {
                    spectrumLayer.changePosition(new Point(locSpectrum.Left, locSpectrum.Top - 64));
                }
                spectrumLayer.shouldCompose = value != 0;
                spectrumLayer.selfCompose = value == 5;
                fftDecayRate = value == 5 ? 0.01f : 0.05f;
            }
        }
        readonly float[] processedFft = new float[200];

        readonly float[] targetFft = new float[200];

        readonly float fftSmoothFactor = 0.4f;

        readonly Pen fftline = createFFTLines1();
        readonly Pen fftline4 = createFFTLines2();
        readonly Pen fftline2 = new Pen(new SolidBrush(Color.FromArgb(192, 255, 255, 255)), 2f) { StartCap = System.Drawing.Drawing2D.LineCap.Round, EndCap = System.Drawing.Drawing2D.LineCap.Round };
        readonly Pen fftline3 = new Pen(new SolidBrush(Color.FromArgb(96, 255, 255, 255)), 1f) { StartCap = System.Drawing.Drawing2D.LineCap.Round, EndCap = System.Drawing.Drawing2D.LineCap.Round };

        private static Pen createFFTLines1()
        {
            ColorBlend cb = new ColorBlend(3);
            cb.Colors[0] = Color.Transparent;
            cb.Colors[1] = Color.White;
            cb.Colors[2] = Color.White;
            cb.Positions[0] = 0;
            cb.Positions[1] = 0.8f;
            cb.Positions[2] = 1;
            LinearGradientBrush brush = new LinearGradientBrush(new Point(0, -1), new Point(0, 128), Color.Transparent, Color.White)
            {
                InterpolationColors = cb
            };
            return new Pen(brush, 2f) { StartCap = LineCap.Round };
        }
        private static Pen createFFTLines2()
        {
            ColorBlend cb = new ColorBlend(3);
            cb.Colors[0] = Color.Transparent;
            cb.Colors[1] = Color.White;
            cb.Colors[2] = Color.Transparent;
            cb.Positions[0] = 0;
            cb.Positions[1] = 0.5f;
            cb.Positions[2] = 1;
            LinearGradientBrush brush = new LinearGradientBrush(new Point(0, -1), new Point(0, 128), Color.Transparent, Color.White)
            {
                InterpolationColors = cb
            };
            return new Pen(brush, 2f) { StartCap = LineCap.Round, EndCap = LineCap.Round };
        }

        PointF[] spectrum5Pts0 = new PointF[100];
        PointF[] spectrum5Pts1 = new PointF[100];
        float spectrum5CachedCx = -1;
        float spectrum5CachedCy = -1;
        float spectrum5CachedBaseLen = -1;
        GraphicsPath spectrum5Path = new GraphicsPath();
        float fftDecayRate = 0.05f;
        const int spectrum5IgnoreLowpass = 7;
        float bassBoostDetectionSmoothed = 0.0f;
        void updateSpectrum()
        {
            Graphics g = spectrumLayer.g;
            g.Clear(Color.Transparent);

            float[] data = player.Spectrum;
            float dataMul = player.SpectrumCorrection;
            int len = processedFft.Length;
            spectrumLayer.shouldCompose = SpectrumMode != 0;
            float bassBoosted = 0;
            float firstAreaAverage = 0f;

            if (SpectrumMode != 0)
            {
                for (int i = 0; i < len; i++)
                {
                    float receivedValue = 0;

                    int span = i / 50;

                    int beginindex;
                    switch (span)
                    {
                        case 0:
                            receivedValue = data[i] * dataMul;
                            break;
                        case 1:
                            beginindex = 2 * i - 50;//(i - 25) * 2 + 25;
                            receivedValue = (data[beginindex] + data[beginindex + 1]) / 2f * dataMul;
                            break;
                        case 2:
                            beginindex = 3 * i - 150;//(i - 50) * 3 + 75;
                            receivedValue = (data[beginindex] + data[beginindex + 1] + data[beginindex + 2]) / 3 * dataMul;
                            break;
                        case 3:
                            beginindex = 4 * i - 300;//(i - 75) * 4 + 150;
                            receivedValue = (data[beginindex] + data[beginindex + 1] + data[beginindex + 2] + (data[beginindex + 2])) / 4 * dataMul;
                            break;
                    }



                    processedFft[i] = Math.Max(0f, Math.Max(processedFft[i] - fftDecayRate, dec2log(Math.Abs(receivedValue))));

                    

                    if (!player.IsPlaying)
                    {
                        processedFft[i] = 0;
                    }
                    targetFft[i] += (processedFft[i] - targetFft[i]) * fftSmoothFactor;
                    if(i >= spectrum5IgnoreLowpass && i < 50 + spectrum5IgnoreLowpass)
                    {
                        firstAreaAverage += targetFft[i];
                    }
                }

                bassBoosted = log2dec((  targetFft[0]+targetFft[1] + targetFft[2] + targetFft[3] + targetFft[4]) / 5) ;

                bassBoostDetectionSmoothed = bassBoostDetectionSmoothed + (bassBoosted - bassBoostDetectionSmoothed) * 0.009f;
                
                if(bassBoosted < bassBoostDetectionSmoothed)
                {
                    bassBoostDetectionSmoothed = bassBoosted;
                }

                bassBoosted = (bassBoosted - bassBoostDetectionSmoothed) / (1 - bassBoostDetectionSmoothed);
            }
            firstAreaAverage -= 0.8f * 8f;

            firstAreaAverage /= 42f;

            if (SpectrumMode == 1 || SpectrumMode == 3)
            {
                for (int i = 0; i < len; i++)
                {
                    if(targetFft[i] < 0.01f)
                    {
                        continue;
                    }
                    float y = 2f + (locSpectrumArea.Width - 4f) / (len - 1) * i;
                    float x1 = locSpectrumArea.Height;
                    float x2 = x1 - targetFft[i] * locSpectrumArea.Height;
                    //fftline.TranslateTransform(x2, x2);
                    g.TranslateTransform(0, x2);
                    g.DrawLine(SpectrumMode == 1 ? fftline2 : fftline, y, 0, y, x1);
                    g.TranslateTransform(0, -x2);
                    //fftline.TranslateTransform(-x2, -x2);
                }
            }
            if (SpectrumMode == 2 || SpectrumMode == 4)
            {
                for (int i = 0; i < len; i++)
                {
                    float multiplier = SpectrumMode == 2 ? (1f - (i / 200f) * 0.94f) : (len - i < 64 ? (len - i) / 64f : 1);

                    float x;
                    if (i % 2 == 0)
                    {
                        x = (locMask.Right - 32) + (orignalWindowSize.Width - locMask.Right + 32) * (i / 2) / 100f - shadowOffset.X;
                    }
                    else
                    {
                        x = locMask.Left + 32 - (locMask.Left + 32) * ((i - 1) / 2) / 100f - shadowOffset.X;
                    }
                    float y1 = 64 - targetFft[i] * multiplier * locSpectrumArea.Height / 2;
                    float y2 = 64 + targetFft[i] * multiplier * locSpectrumArea.Height / 2;
                    if (SpectrumMode == 2)
                    {
                        g.DrawLine(fftline2, x, y2, x, y1);
                    }
                    else
                    {
                        g.TranslateTransform(0, y1);
                        float scale;
                        if (y2 - y1 < 0.5)
                        {
                            scale = 1f / 1024f;
                        }
                        else
                        {
                            scale = (y2 - y1) / 128f;
                        }
                        g.ScaleTransform(1, scale);
                        g.DrawLine(fftline4, x, 0, x, 128);
                        g.ResetTransform();
                    }
                }
                //for (int i = 1; i < len; i += 2)
                //{

                //    float multiplier = 1f - ((i - 1) / 200f) * 0.94f;
                //    float x = locMask.Left + 32 - (locMask.Left + 32) * ((i - 1) / 2) / 100f;
                //    float y1 = 64 - targetFft[i] * multiplier * locSpectrumArea.Height / 2;
                //    float y2 = 64 + targetFft[i] * multiplier * locSpectrumArea.Height / 2;
                //    g.DrawLine(fftline2, x, y2, x, y1);
                //}
                g.DrawLine(fftline3, 0, 64, 768, 64);
            }

            if(SpectrumMode == 5)
            {
                g = this.gdi.Graphics;

                if(spectrum5CachedCx < 0)
                {
                    spectrum5CachedCx = locGlowing.Left + locGlowing.Width / 2;
                    spectrum5CachedCy = locGlowing.Top + locGlowing.Height / 2;
                    spectrum5CachedBaseLen = locGlowing.Height / 2;
                }

                float centerX = spectrum5CachedCx;
                float centerY = spectrum5CachedCy;
                g.ResetTransform();

#if DEBUG

                g.DrawRectangle(fftline2, 30, 140, 100, 30);
                g.DrawRectangle(fftline2, 30, 140, 100f * bassClipThrehold, 20);
                g.DrawRectangle(fftline2, 30, 140, 100 * (bassClip(bassBoosted)), 10);
                g.DrawRectangle(fftline2, 30, 150, 100 * ((bassBoosted)), 10);
                g.DrawRectangle(fftline2, 30, 160, 100 * ((bassBoostDetectionSmoothed)), 10);
#endif
                for (int i = 0; i < 50; i ++) {
                    float angle = (float)(Math.PI * ((double)i / (double)50) + discangel / 360d * Math.PI) ;

                    i += spectrum5IgnoreLowpass;
                    float fftval = targetFft[i*2] > targetFft[i*2 + 1] ? targetFft[i*2] : targetFft[i*2 + 1];
                    i -= spectrum5IgnoreLowpass;
                    fftval *=  castSpectrum5(bassClip(bassBoosted) + (fftval - firstAreaAverage * 0.86f) / (1.05f - firstAreaAverage) * (1f + ((float)i / 50f)));

                    float lenBase = spectrum5CachedBaseLen;
                    float len1 = lenBase - lenBase * 0.15f * fftval;
                    float len2 = lenBase + lenBase * 0.35f * fftval;

                    PointF pt0 = new PointF((float)(Math.Sin(angle + Math.PI / 2) * len1) +centerX, -(float)(Math.Cos(angle + Math.PI / 2) * len1) + centerY);
                    PointF pt1 = new PointF((float)(Math.Sin(angle + Math.PI / 2) * len2) + centerX, -(float)(Math.Cos(angle + Math.PI / 2) * len2) + centerY);


                    PointF pt2 = new PointF((float)(Math.Sin(angle - Math.PI / 2) * len1) + centerX, -(float)(Math.Cos(angle - Math.PI / 2) * len1) + centerY);
                    PointF pt3 = new PointF((float)(Math.Sin(angle - Math.PI / 2) * len2) + centerX, -(float)(Math.Cos(angle - Math.PI / 2) * len2) + centerY);


                    g.DrawLine(fftline2, pt0, pt1);
                    g.DrawLine(fftline2, pt2, pt3);

                    spectrum5Pts0[i] = pt0;
                    spectrum5Pts1[i] = pt1;
                    spectrum5Pts0[i+50] = pt2;
                    spectrum5Pts1[i+50] = pt3;

                }

                spectrum5Path.Reset();
                spectrum5Path.AddPolygon(spectrum5Pts0);
                g.DrawPath(fftline2,spectrum5Path);

                spectrum5Path.Reset();
                spectrum5Path.AddPolygon(spectrum5Pts1);
                g.DrawPath(fftline2, spectrum5Path);
                // spectrum5Path.CloseFigure();
                if(spectrum5Decorations == null)
                {
                    spectrum5Decorations = new Spectrum5Decoration[20];
                    spectrum5DecorationInitSeq = 0;
                }
                if(spectrum5DecorationInitSeq / 70 < spectrum5Decorations.Length - 1)
                {
                    spectrum5DecorationInitSeq++;
                }   
                for (int i = 0; i < spectrum5DecorationInitSeq / 70; i++)
                {
                    if(spectrum5Decorations[i] == null)
                    {
                        spectrum5Decorations[i] = new Spectrum5Decoration();
                        spectrum5Decorations[i].reset();
                    }
                    spectrum5Decorations[i].Draw(g, spectrum5CachedCx, spectrum5CachedCy, spectrum5CachedBaseLen);
                }
            }

            if (BassPlayerImpl.debug)
            {
                g.CompositingMode = CompositingMode.SourceCopy;
                g.FillRectangle(Brushes.Transparent, 1, 94, 196, 20);
                g.CompositingMode = CompositingMode.SourceOver;
                g.DrawRectangle(Pens.White, 3, 96, 192, 16);
                g.FillRectangle(white, 3, 96, (player.peakDB + 96f) * 2f, 16f);

                g.DrawString(player.peakDB.ToString("00.0") + " dB", Font, Brushes.Black, 6, 100);
                g.DrawString(player.peakDB.ToString("00.0") + " dB", Font, Brushes.LightGray, 5, 99);
            }
            if (SpectrumMode != 5)
            {
                spectrumLayer.UpdateWindow();
            }
        }

        Spectrum5Decoration[] spectrum5Decorations = null;
        int spectrum5DecorationInitSeq = 0;

        class Spectrum5Decoration
        {
            public float distance = 0;
            public float distanceVelocity = 1;
            public float distanceDx = 0.5f;
            public float distanceDy = 0.5f;
            public float rotationSpeed = 0.5f;
            public int alpha = 1;
            public int passingTicks = 0;
            public int passingTickThrehold = 60;

            public float rotation = 0;
            public float rad0 = 0;
            public float rad1 = 0;
            public float rad2 = 0;
            public float dist0 = 1;
            public float dist1 = 1;
            public float dist2 = 1;

            public const float shapeSize = 14;
            public const float rotationSpeedBase = 0.007f;
            public const float movingSpeedBase = 0.3f;

            private static Random rnd = new Random();
            private static float initialRotationBase = 3;
            public void reset()
            {
                distance = -28;
                passingTicks = 0;
                alpha = 232;
                var rad = rnd.NextDouble() * Math.PI / 4 + initialRotationBase;
                initialRotationBase += (float)(Math.PI + Math.PI / 7d);
                if(initialRotationBase >= Math.PI * 2)
                {
                    initialRotationBase -= (float)(Math.PI * 2);
                }
                distanceVelocity = (float)(rnd.NextDouble() * 0.3d + 0.7d);
                passingTickThrehold = (int)(distanceVelocity * 60 + distance + rnd.Next(30) + 80);
                distanceDx = (float)Math.Cos(rad);
                distanceDy = (float)Math.Sin(rad);
                rotation = (float)(rnd.NextDouble() * Math.PI);
                rotationSpeed = (float)(rnd.NextDouble() * 3 + 0.4);
                if(rnd.Next(100) % 2 == 0)
                {
                    rotationSpeed = -rotationSpeed;
                }
                rad0 = (float)(rnd.NextDouble() * Math.PI / 3d);
                rad1 = (float)(rnd.NextDouble() * Math.PI / 3d + Math.PI / 3f * 2f);
                rad2 = (float)(rnd.NextDouble() * Math.PI / 3d + Math.PI / 3f * 4f);
                dist0 = (float)(rnd.NextDouble() * 0.5 + 0.5) * shapeSize;
                dist1 = (float)(rnd.NextDouble() * 0.5 + 0.5) * shapeSize;
                dist2 = (float)(rnd.NextDouble() * 0.5 + 0.5) * shapeSize;
            }
            Pen p = new Pen(new SolidBrush(Color.White), 1.2f) { 
                LineJoin = LineJoin.Round,
                StartCap = LineCap.Round,
                EndCap = LineCap.Round,
            };
            public void Draw(Graphics g,float cx,float cy,float distanceBase)
            {
                passingTicks++;
                if (passingTicks > passingTickThrehold)
                {
                    alpha-=2;
                    if(alpha <= 0)
                    {
                        reset();
                        return;
                    }
                }
                rotation += rotationSpeed * rotationSpeedBase;
                if(rotation > Math.PI * 2)
                {
                    rotation -= (float)(Math.PI * 2);
                }
                distance += distanceVelocity * movingSpeedBase;
                var baseX = cx + (distanceBase + distance) * distanceDx;
                var baseY = cy + (distanceBase + distance) * distanceDy;

                PointF[] pts = new PointF[3];
                pts[0] = getPoint(baseX, baseY,rad0,dist0);
                pts[1] = getPoint(baseX, baseY,rad1,dist1);
                pts[2] = getPoint(baseX, baseY,rad2,dist2);
                p.Color = Color.FromArgb(alpha, 255, 255, 255);
                g.DrawPolygon(p, pts);

            }

            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            PointF getPoint(float baseX,float baseY, float rad,float dist)
            {
                var dx = (float)Math.Cos(rad + rotation) * dist  * (distance + 100)/ 100f ;
                var dy = (float)Math.Sin(rad + rotation) * dist  * (distance + 100) / 100f;
                return new PointF(dx + baseX, dy + baseY);
            }

        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        float castSpectrum5(float input)
        {
            if(input > 0f)
            {
                return input; 
            }
            else
            {
                return 0;
            }
        }

        const float bassClipThrehold = 0.09f;
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        float bassClip(float input)
        {
            if(input < bassClipThrehold)
            {
                return 0f;
            }
            else
            {
                return (input - bassClipThrehold) / (1 - bassClipThrehold) * 2;
            }
        }

        float dec2log(float x) => x == 0 ? 0 : Math.Max(0, (float)(Math.Log10(x * 10000) / 4));
        float log2dec(float x) => (float)Math.Pow(10,(x * 4)) / 10000;



        private readonly Bitmap imgFavOn = new Bitmap(Properties.Resources.heart_fill, 28, 28);
        private readonly Bitmap imgFavOff = new Bitmap(Properties.Resources.heart, 28, 28);

        bool adjustVolume = false;
        private void volumnDown(object sender, MouseEventArgs e)
        {
            adjustVolume = true;
            if (adjustVolume)
            {
                float targetVolume = 1f - (e.Y - locVolumnMaxPoint.Top) / (float)(locVolumeMinPoint.Top - locVolumnMaxPoint.Top);
                if (targetVolume > 1) { targetVolume = 1; }
                if (targetVolume < 0) { targetVolume = 0; }
                player.Volume = targetVolume * targetVolume;
            }
        }

        private void volumeMove(object sender, MouseEventArgs e)
        {
            if (adjustVolume)
            {
                float targetVolume = 1f - (e.Y - locVolumnMaxPoint.Top) / (float)(locVolumeMinPoint.Top - locVolumnMaxPoint.Top);
                if (targetVolume > 1) { targetVolume = 1; }
                if (targetVolume < 0) { targetVolume = 0; }
                player.Volume = targetVolume * targetVolume;
            }
        }

        private void volumeUp(object sender, MouseEventArgs e)
        {
            adjustVolume = false;
            if (adjustVolume)
            {
                float targetVolume = 1f - (e.Y - locVolumnMaxPoint.Top) / (float)(locVolumeMinPoint.Top - locVolumnMaxPoint.Top);
                if (targetVolume > 1) { targetVolume = 1; }
                if (targetVolume < 0) { targetVolume = 0; }
                player.Volume = targetVolume * targetVolume;
            }
        }


        float sliderAnimationCountdown = 0;
        bool sliderAnimationFadein = false;

        readonly Bitmap sliderVolumnBg = new Bitmap(Properties.Resources.bg_volumeSlider, 60, 201);
        readonly Bitmap sliderUtilBg = new Bitmap(Properties.Resources.bg_extendControl, 60, 201);
        readonly Bitmap spectrumBottom = new Bitmap(Properties.Resources.ic_spectrum_bottom, 32, 32);
        readonly Bitmap spectrumDisable = new Bitmap(Properties.Resources.ic_spectrum_disable, 32, 32);
        readonly Bitmap spectrumCenter = new Bitmap(Properties.Resources.ic_spectrum_center, 32, 32);

        readonly Bitmap srsOn = new Bitmap(Properties.Resources.sr_on, 32, 32);
        readonly Bitmap rsrOn = new Bitmap(Properties.Resources.rs_on, 32, 32);
        readonly Bitmap srsOff = new Bitmap(Properties.Resources.sr_off, 32, 32);

        int dsptype = 0;

        void updateSliderControl()
        {
            Graphics bg = siderBackgroundLayer.g;
            Graphics fg = siderForegroundLayer.g;
            if (tblVolumn.Visible)
            {

                siderBackgroundLayer.shouldCompose = true;
                siderForegroundLayer.shouldCompose = true;
                bg.Clear(Color.Transparent);
                fg.Clear(Color.Transparent);
                if (sliderAnimationCountdown > 0)
                {
                    sliderAnimationCountdown -= 0.072f;
                    if (sliderAnimationCountdown < 0)
                    {
                        sliderAnimationCountdown = 0;
                        if (!sliderAnimationFadein)
                        {
                            tblVolumn.Visible = false;
                        }
                    }

                }
                float x = locVolumnMaxPoint.Left;
                float y = locVolumeMinPoint.Top - (locVolumeMinPoint.Top - locVolumnMaxPoint.Top) * (float)Math.Sqrt(player.Volume);
                if (sliderAnimationFadein)
                {
                    DrawUtils.drawAlphaImage(bg, sliderVolumnBg, 60 - deceleration(1 - sliderAnimationCountdown) * 60, 0, 60, 201, 1 - sliderAnimationCountdown);
                    DrawUtils.drawAlphaImage(fg, progressThumb, x - 20 + (60 - deceleration(1 - sliderAnimationCountdown) * 60), y - 20, 43, 43, 1 - sliderAnimationCountdown);
                }
                else
                {
                    DrawUtils.drawAlphaImage(bg, sliderVolumnBg, acceleration(1 - sliderAnimationCountdown) * 60, 0, 60, 201, sliderAnimationCountdown);
                    DrawUtils.drawAlphaImage(fg, progressThumb, x - 20 + (acceleration(1 - sliderAnimationCountdown) * 60), y - 20, 43, 43, sliderAnimationCountdown);
                }


                siderBackgroundLayer.UpdateWindow();
                siderForegroundLayer.UpdateWindow();
            }
            else if (tblUtils.Visible)
            {

                siderBackgroundLayer.shouldCompose = true;
                siderForegroundLayer.shouldCompose = true;
                bg.Clear(Color.Transparent);
                fg.Clear(Color.Transparent);
                if (sliderAnimationCountdown > 0)
                {
                    sliderAnimationCountdown -= 0.072f;
                    if (sliderAnimationCountdown < 0)
                    {
                        sliderAnimationCountdown = 0;
                        if (!sliderAnimationFadein)
                        {
                            tblUtils.Visible = false;
                        }
                    }

                }

                float offsetx;
                float alpha;

                if (sliderAnimationFadein)
                {
                    offsetx = 60 - deceleration(1 - sliderAnimationCountdown) * 60;
                    alpha = 1 - sliderAnimationCountdown;
                }
                else
                {
                    offsetx = acceleration(1 - sliderAnimationCountdown) * 60;
                    alpha = sliderAnimationCountdown;
                }

                DrawUtils.drawAlphaImage(bg, sliderUtilBg, offsetx, 0, 60, 201, alpha);
                if (loopMode == 0) { DrawUtils.drawAlphaImage(fg, ic_loop_all, btnLoopMode.Left + offsetx, btnLoopMode.Top, btnLoopMode.Width, btnLoopMode.Height, alpha); }
                if (loopMode == 1) { DrawUtils.drawAlphaImage(fg, ic_loop_one, btnLoopMode.Left + offsetx, btnLoopMode.Top, btnLoopMode.Width, btnLoopMode.Height, alpha); }
                if (loopMode == 2) { DrawUtils.drawAlphaImage(fg, ic_loop_list, btnLoopMode.Left + offsetx, btnLoopMode.Top, btnLoopMode.Width, btnLoopMode.Height, alpha); }
                DrawUtils.drawAlphaImage(fg, shuffeMode ? ic_shuffe_on : ic_shuffe_off, btnShuffe.Left + offsetx, btnShuffe.Top, btnLoopMode.Width, btnLoopMode.Height, alpha);

                Bitmap spectrumMode = SpectrumMode == 0 ? spectrumDisable : (SpectrumMode == 1 ? spectrumBottom : spectrumCenter);

                DrawUtils.drawAlphaImage(fg, spectrumMode, btnShuffe.Left + offsetx, btnSpectrumMode.Top, btnLoopMode.Width, btnLoopMode.Height, alpha);

                if (dsptype == 0) { DrawUtils.drawAlphaImage(fg, srsOff, btnShuffe.Left + offsetx, btnSrs.Top, btnLoopMode.Width, btnLoopMode.Height, alpha); }
                if (dsptype == 1) { DrawUtils.drawAlphaImage(fg, srsOn, btnShuffe.Left + offsetx, btnSrs.Top, btnLoopMode.Width, btnLoopMode.Height, alpha); }
                if (dsptype == 2) { DrawUtils.drawAlphaImage(fg, rsrOn, btnShuffe.Left + offsetx, btnSrs.Top, btnLoopMode.Width, btnLoopMode.Height, alpha); }





                siderBackgroundLayer.UpdateWindow();
                siderForegroundLayer.UpdateWindow();
            }
            else
            {
                siderBackgroundLayer.shouldCompose = false;
                siderForegroundLayer.shouldCompose = false;
            }

        }

        public float deceleration(float input) => (float)(1.0f - (1.0f - input) * (1.0f - input));

        public float acceleration(float input) => input * input;

        public float easeOut(float input)
        {

            return (float)(1 - Math.Pow(1 - input, 5) * (2 - (1 - input)));

        }
        readonly Size __vertical_sync_temp_size = new Size(1, 1);
        private void renderTimer_Tick(object sender, EventArgs e)
        {
            if (isShowing)
            {

#if DEBUG
                DateTime begin = DateTime.UtcNow;
#endif
                if (crossfadeCountdown > 0)
                {
                    updateBackground();
                }

                if (spectrumLayer.shouldCompose && _spectrumMode != 5)
                {
                    updateSpectrum();
                }

                updateSliderControl();
                listLayer.shouldCompose = tblList.Visible;
                if (listLayer.shouldCompose)
                {
                    updateListLayer();
                }
#if DEBUG
                double renderMS = (DateTime.UtcNow - begin).TotalMilliseconds;
                renderTime[renderTimeIndex = (renderTimeIndex + 1) % renderTime.Length] = renderMS;
#endif
                composeAllLayers();
                if (listLayer.shouldCompose)
                {
                    gdi.WaitForVerticalSync();// 仅当显示列表时垂直同步。其它的元素没有垂直同步的必要
                }
            }
            updateLyric();
        }

        bool drawedShadow = false;
#if DEBUG
        readonly double[] renderTime = new double[20];
        readonly double[] composeTime = new double[20];
        int renderTimeIndex = 0, composeTimeIndex = 0;
        readonly FPSCounter fPSCounter = new FPSCounter();
#endif
        private void composeAllLayers()
        {
            if (!drawedShadow)
            {
                gdi.Graphics.DrawImageUnscaled(shadowImage, Point.Empty);
                drawedShadow = true;
            }
            int composeCount = 0;
            DateTime begin = DateTime.UtcNow;
            for (int i = 0; i < layers.Count; i++)
            {
                GraphicsLayer graphicsLayer = layers[i];
                if (!graphicsLayer.isActualWindow)
                {
                    if (graphicsLayer.shouldCompose)
                    {
                        if (graphicsLayer.selfCompose)
                        {
                            graphicsLayer.selfComposeAction();
                        }
                        else
                        {

                            Point pt = graphicsLayer.relativeRect.Location;
                            pt.Offset(shadowOffset);
                            gdi.Graphics.DrawImageUnscaled(graphicsLayer.memDC, pt);
                            composeCount++;
                        }
                    }
                }

            }


#if DEBUG

            double composeMS = (DateTime.UtcNow - begin).TotalMilliseconds;
            composeTime[composeTimeIndex = (composeTimeIndex + 1) % composeTime.Length] = composeMS;

            gdi.Graphics.DrawString("FPS:" + fPSCounter.FPS + ", Layers: " + composeCount + ", Render: " + renderTime.Average().ToString("0.000") + "ms, Compose+DirectRender: " + composeTime.Average().ToString("0.000") + "ms", lblTitle.Font, white, 100, 70);
#endif
            gdi.UpdateWindow();
        }



        readonly Bitmap ic_loop_one = new Bitmap(Properties.Resources.ic_loop_one);//0

        private void btnLoopMode_Click(object sender, EventArgs e)
        {
            loopMode++;
            if (loopMode > 2) { loopMode = 0; }
            player.Looping = loopMode == 1;
        }

        private void btnShuffe_Click(object sender, EventArgs e)
        {
            shuffeMode = !shuffeMode;
        }

        private void btnPrev_Click(object sender, EventArgs e)
        {
            controller.onPrevButtonClick();
        }

        private void btnNext_Click(object sender, EventArgs e)
        {
            controller.onNextButtonClick();
        }

        private void btnPreserved1_Click(object sender, EventArgs e)
        {
            SpectrumMode++;
            if (SpectrumMode > 5) { SpectrumMode = 0; }
            //player.SkipFFT = SpectrumMode == 0;
        }

        readonly Bitmap ic_loop_all = new Bitmap(Properties.Resources.ic_loop_all);//1
        readonly Bitmap ic_loop_list = new Bitmap(Properties.Resources.ic_loop_list);//2

        int loopMode { get => controller.LoopMode; set => controller.LoopMode = value; }

        readonly Bitmap ic_shuffe_on = new Bitmap(Properties.Resources.ic_shuffe);
        readonly Bitmap ic_shuffe_off = new Bitmap(Properties.Resources.ic_shuffe_off);

        bool shuffeMode { get => controller.Shuffe; set => controller.Shuffe = value; }
        public bool Bassboosted
        {
            get => player.BassBoost; set
            {
                player.BassBoost = value;
            }
        }



        float discangel = 0.0f;
        float lastDiscAngel = 0.0f;
        GraphicsPath coverClipPath = null;
        void updateSpinningDisc()
        {
            lightLumianceOverlay += 0.006f;
            while (lightLumianceOverlay > 1) { lightLumianceOverlay -= 1; }

            float finalAlpha = 1;
            if (lightLumianceOverlay < 0.5) { finalAlpha -= lightLumianceOverlay; }
            if (lightLumianceOverlay >= 0.5) { finalAlpha -= (1 - lightLumianceOverlay); }


            DrawUtils.drawAlphaImage(gdi.Graphics, coverGlowing, locGlowing.Left, locGlowing.Top, locGlowing.Width, locGlowing.Height, finalAlpha);

            if(coverClipPath == null)
            {
                coverClipPath = new GraphicsPath();
                coverClipPath.AddEllipse(locMask.Left - 1, locMask.Top - 1, discDisplayLayer.relativeRect.Width + 1, discDisplayLayer.relativeRect.Height + 1);
            }
            gdi.Graphics.SetClip(coverClipPath);
            if (crossfadeCountdown > 0)
            {

                if (oldCover != null)
                {

                    DrawUtils.drawRotateImg(gdi.Graphics, oldCover, lastDiscAngel, locMask.Left + discDisplayLayer.relativeRect.Width / 2, locMask.Top + discDisplayLayer.relativeRect.Height / 2);
                }
                DrawUtils.drawAlphaImage(gdi.Graphics, currentCover, locMask.Left - 1, locMask.Top - 1, discDisplayLayer.relativeRect.Width + 1, discDisplayLayer.relativeRect.Height + 1, (1 - crossfadeCountdown));

            }
            else
            {
                if (player.IsPlaying) { discangel += 0.2f; }
                if(discangel > 360f)
                {
                    discangel -= 360f;
                }

                DrawUtils.drawRotateImg(gdi.Graphics, currentCover, discangel, locMask.Left + discDisplayLayer.relativeRect.Width / 2, locMask.Top + discDisplayLayer.relativeRect.Height / 2);
            }

            gdi.Graphics.ResetClip();

        }


        Bitmap listSurface = null;



        Graphics listDc = null;
        float listAnimationCountdown = 0;
        bool isExpanding = false;
        void updateListLayer()
        {
            if (null == listSurface)
            {
                listSurface = new Bitmap(tblList.Width, tblList.Height);
                listDc = Graphics.FromImage(listSurface);
                listDc.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
                listDc.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
                listDc.CompositingQuality = CompositingQuality.GammaCorrected;
                tblList.MouseWheel += TblList_MouseWheel;
            }
            Graphics g = listLayer.g;
            g.Clear(Color.Transparent);
            drawList();

            float offsetx = 0;
            if (listAnimationCountdown > 0)
            {

                if (isExpanding)
                {
                    offsetx = -tblList.Width * (1f - easeOut(1 - listAnimationCountdown));
                    listAnimationCountdown -= 0.035f;
                }
                else
                {
                    offsetx = -tblList.Width * acceleration(1 - listAnimationCountdown);
                    listAnimationCountdown -= 0.07f;
                }

                g.DrawImage(listSurface, 0 + offsetx, 0f);
            }
            else
            {
                offsetx = 0;
                if (!isExpanding)
                {
                    tblList.Visible = false;
                    listHidedAction?.Invoke();
                }
                else
                {

                    g.DrawImage(listSurface, 0, 0f);
                }
            }

            listLayer.UpdateWindow();
        }

        private void TblList_MouseWheel(object sender, MouseEventArgs e)
        {
            if (e.Delta < 0)
            {
                velotry = 0.2f;
            }
            else
            {
                velotry = -0.2f;
            }
        }

        bool isScrollBar = false;
        bool hasClickEvent = false;
        float beginClickX, beginClickY;

        bool clicked = false;

        private void listMouseDown(object sender, MouseEventArgs e)
        {
            clicked = true;
            isScrollBar = e.X > tblList.Width - scrollbarSize;
            hasClickEvent = false;
            beginClickX = e.X;
            beginClickY = e.Y;
            nowY = beginClickY;
            velotry = 0;
        }

        float  nowY;
        float dragspeed = 0;
        float requestDraggingDistance = 0;
        float requestDraggingVelotry = 0;
        float requestScrollbar = 0;
        private void listMouseMove(object sender, MouseEventArgs e)
        {
            if (clicked)
            {
                float y = e.Y;

                dragspeed = y - nowY;
                if (!isScrollBar)
                {
                    requestDraggingDistance -= dragspeed;
                }
                else
                {
                    requestScrollbar += dragspeed;
                }
                nowY = y;
            }
        }



        float lastClickX, lastClickY;
        private void listMouseUp(object sender, MouseEventArgs e)
        {

            float x = e.X;
            float y = e.Y;
            if (Math.Sqrt(Math.Pow(x - beginClickX, 2) + Math.Pow(y - beginClickY, 2)) > 5)
            {
                if (!isScrollBar)
                {
                    requestDraggingVelotry = -dragspeed;
                }
            }
            else
            {
                lastClickX = x; lastClickY = y;
                hasClickEvent = true;
            }
            clicked = false;
        }

        float velotry = 0f;
        float position = 0f;

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            controller.PlayListProvider.saveProgress();
            Properties.Settings.Default.playmode = controller.LoopMode;
            Properties.Settings.Default.shuffemode = controller.Shuffe;
            Properties.Settings.Default.bassboost = player.BassBoost;
            Properties.Settings.Default.spectrummode = SpectrumMode + (_showDesktopLyrics ? 1 : 0) * 100 + (ShowTrayIcons ? 1 : 0) * 1000 ;
            Properties.Settings.Default.dsptype = this.dsptype + ((player.DynamicRangeCompressed % 10) * 10);
            Properties.Settings.Default.Save();
            UnregisterHotKey(this.Handle, 2);
            UnregisterHotKey(this.Handle, 3);
            UnregisterHotKey(this.Handle, 4);
        }

        private void Form1_Click(object sender, EventArgs e)
        {
            if (tblVolumn.Visible) { btnVolume_Click(sender, e); }
            if (tblUtils.Visible) { btnMore_Click(sender, e); }
            if (tblList.Visible) { hideList(null); }
        }

        private void btnPreserved2_Click(object sender, EventArgs e)
        {
            if (tblList.Visible)
            {
                if (currentlist is FxAdapter)
                {
                    hideList(null);
                }
                else
                {
                    hideList(() =>
                    {
                        showList(new FxAdapter(this));
                        position = controller.AllPlayList.IndexOf(controller.CurrentList) - 5;
                        velotry = 0.01f;
                    });
                }
            }
            else
            {
                showList(new FxAdapter(this));
                position = controller.AllPlayList.IndexOf(controller.CurrentList) - 5;
                velotry = 0.01f;
            }
        }

        readonly float scrollbarSize = 16;

        readonly Pen listDividerLine = new Pen(new SolidBrush(Color.FromArgb(128, 255, 255, 255)), 1);
        readonly Brush scrollbar = new SolidBrush(Color.FromArgb(128, 255, 255, 255));

        void drawList()
        {
            Graphics g = listDc;
            g.Clear(Color.FromArgb(192, 0, 0, 0));
            if (currentlist != null)
            {
                IListAdapter list = currentlist;
                float itemheight = list.getItemHeight();
                float totalItems = list.getCount();
                velotry *= 0.976f;
                if (velotry < 0.001f && velotry >= -0.001f)
                {
                    velotry = 0;
                }
                float maxPosition = totalItems - tblList.Height / itemheight;

                position += velotry;
                if (requestDraggingDistance != 0)
                {
                    position += requestDraggingDistance / itemheight;
                    requestDraggingDistance = 0;
                }
                if (requestDraggingVelotry != 0)
                {
                    velotry = requestDraggingVelotry / itemheight;
                    requestDraggingVelotry = 0;
                }
                if (requestScrollbar != 0 && maxPosition > 0)
                {

                    float scrollbarLength = tblList.Height * ((tblList.Height / itemheight) / totalItems);

                    position += maxPosition / (tblList.Height - scrollbarLength) * requestScrollbar;
                    requestScrollbar = 0;
                }


                if (position < 0) { position = 0; if (velotry < 0) { velotry = 0; } }
                if (position > Math.Max(0, maxPosition)) { position = Math.Max(0, maxPosition); velotry = 0; }

                int beginItem = (int)position / 1;
                int actualBeginItem = (int)position / 1;
                beginItem = 0;
                int endItem = Math.Min(list.getCount() - 1, beginItem + (int)(tblList.Height / itemheight + 2));
                int actualEndItem = Math.Min(list.getCount() - 1, actualBeginItem + (int)(tblList.Height / itemheight + 2));
                endItem = list.getCount() - 1;
                float beginoffset = -(1 - position);
                for (int i = actualBeginItem; i <= actualEndItem; i++)
                {
                    float bx = 0, by = ((i - beginItem - 1) - beginoffset) * itemheight , bw = tblList.Width - scrollbarSize, bh = itemheight;
                    bool hasClick = false;
                    if (hasClickEvent && new RectangleF(bx, by, bw, bh).Contains(lastClickX, lastClickY))
                    {
                        hasClick = true;
                        hasClickEvent = false;
                    }
                    list.drawItem(g, i, bx, by, bw, bh, hasClick);
                    g.DrawLine(listDividerLine, 0, by + bh, tblList.Width, by + bh);
                }
                if (maxPosition > 0)
                {
                    float scrollbarLength = tblList.Height * ((tblList.Height / itemheight) / totalItems);
                    float scrollbarPosition = (tblList.Height - scrollbarLength) * (position / maxPosition);
                    g.FillRectangle(scrollbar, tblList.Width - scrollbarSize, 0, scrollbarSize, tblList.Height);
                    g.FillRectangle(scrollbar, tblList.Width - scrollbarSize, scrollbarPosition, scrollbarSize, scrollbarLength);
                }
            }
        }


        private void btnSrs_Click(object sender, EventArgs e)
        {
            dsptype++;
            if (dsptype > 2) { dsptype = 0; }
            if (dsptype == 0)
            {
                ((DspSwitcher)player.SurroundSound).WrappedDSP = null;
            }
            if (dsptype == 1)
            {
                ((DspSwitcher)player.SurroundSound).WrappedDSP = new SurroundDSP();
            }
            if (dsptype == 2)
            {
                ((DspSwitcher)player.SurroundSound).WrappedDSP = new SpeakerInRoomDSP();
                if (Properties.Settings.Default.fxfile.ToLower().EndsWith(".wav"))
                {
                    Properties.Settings.Default.fxfile = null;
                    Properties.Settings.Default.Save();
                }
            }


        }


        public bool isFavChecked = false;
        void checkFavStatus()
        {
            isFavChecked = favourite.Songs.Contains(controller.CurrentPlaying);
        }

        private readonly VirtualDir favourite = new VirtualDir("收藏", Path.GetFullPath("收藏.pl"), Path.GetFullPath("."));



        private void btnFav_Click(object sender, EventArgs e)
        {
            if (!isFavChecked)
            {
                List<SongEntry> refSongs = favourite.Songs;
                refSongs.Add(controller.CurrentPlaying);
                favourite.save();

                if (controller.PlayListProvider is CurrentDirectorySongProvider provider)
                {
                    provider.UpdatePlaylist();
                }
            }
            else
            {
                List<SongEntry> refSongs = favourite.Songs;
                refSongs.Remove(controller.CurrentPlaying);
                favourite.save();

                if (controller.PlayListProvider is CurrentDirectorySongProvider provider)
                {
                    provider.UpdatePlaylist();
                }
            }
            checkFavStatus();
            updateTopControl();
        }

        IListAdapter currentlist = null;

        interface IListAdapter
        {
            float getItemHeight();
            int getCount();
            object getItem(int position);
            void drawItem(Graphics g, int position, float bx, float by, float w, float h, bool clicked);
        }

        class FolderAdapter : IListAdapter
        {
            readonly Form1 _this;

            public FolderAdapter(Form1 @this)
            {
                _this = @this;
            }

            readonly Brush white = Brushes.White;
            readonly Brush yellow = Brushes.Yellow;

            readonly Bitmap playlistImg = new Bitmap(Properties.Resources.img_folder, 26, 26);
            readonly Bitmap playlistImg2 = new Bitmap(Properties.Resources.ic_playlist, 26, 26);

            public void drawItem(Graphics g, int position, float bx, float by, float w, float h, bool clicked)
            {
                RectangleF textRect = new RectangleF(bx + 36, by, w - 36, h);
                PlayList pl = (PlayList)getItem(position);
                g.DrawString(pl.Name, _this.lblArtsit.Font, pl == _this.controller.CurrentList ? yellow : white, textRect, _this.alignLeft);
                g.DrawImage(pl is VirtualDir ? playlistImg2 : playlistImg, 3, 3 + by);
                if (clicked)
                {
                    _this.hideList(() => _this.showList(new SongAdapter(_this, pl)));
                }
            }

            public int getCount()
            {
                return _this.controller.AllPlayList.Count;
            }

            public object getItem(int position)
            {
                return _this.controller.AllPlayList[position];
            }

            public float getItemHeight()
            {
                return 32;
            }
        }

        class FxAdapter : IListAdapter
        {
            readonly Form1 _this;
            private readonly List<string> fxfiles = new List<string>();
            public FxAdapter(Form1 @this)
            {
                _this = @this;
                fxfiles.Add("");
                if (Directory.Exists(".musicfx"))
                {
                    Directory.EnumerateFiles(".musicfx", "*.wav").ToList().ForEach(f => fxfiles.Add(Path.GetFileName(f)));
                    Directory.EnumerateFiles(".musicfx", "*.eq").ToList().ForEach(f => fxfiles.Add(Path.GetFileName(f)));
                }
            }

            readonly Brush white = Brushes.White;

            readonly Bitmap playlistImg = new Bitmap(Properties.Resources.ic_spectrum_bottom, 26, 26);

            public void drawItem(Graphics g, int position, float bx, float by, float w, float h, bool clicked)
            {
                RectangleF textRect = new RectangleF(bx + 36, by, w - 36, h);
                string item = fxfiles[position];
                string displayName = String.IsNullOrEmpty(item) ? "<无音效>" : item;
                g.DrawString(displayName, _this.lblArtsit.Font, white, textRect, _this.alignLeft);
                g.DrawImage(playlistImg, 3, 3 + by);
                if (clicked)
                {
                    _this.hideList(() =>
                    {
                        string fxpath = fxfiles[position];
                        if (fxpath != null && fxpath != "")
                        {
                            fxpath = Path.Combine(".musicfx", fxpath);

                        }
                        try
                        {
                            _this.player.LoadFx(fxpath);
                            Properties.Settings.Default.fxfile = fxpath;
                            if (fxpath.ToLower().EndsWith(".wav"))
                            {
                                Properties.Settings.Default.dsptype = 0 + (_this.player.DynamicRangeCompressed % 10);
                                _this.dsptype = 0;
                            }
                            Properties.Settings.Default.Save();
                        }
                        catch (Exception ex)
                        {
                            MessageBox.Show(ex.Message, "加载FX失败");
                        }
                    });
                }
            }

            public int getCount()
            {
                return fxfiles.Count;
            }

            public object getItem(int position)
            {
                return fxfiles[position];
            }

            public float getItemHeight()
            {
                return 32;
            }
        }

        class SongAdapter : IListAdapter
        {
            readonly Form1 _this;
            readonly PlayList usingList;
            public SongAdapter(Form1 @this, PlayList pl)
            {
                _this = @this;
                usingList = pl;
            }

            readonly Brush white = Brushes.White;
            readonly Brush yellow = Brushes.Yellow;

            readonly Bitmap playlistImg = new Bitmap(Properties.Resources.ic_song, 30, 30);

            public void drawItem(Graphics g, int position, float bx, float by, float w, float h, bool clicked)
            {
                RectangleF textRect = new RectangleF(bx + 36, by, w - 36, h);
                SongEntry pl = (SongEntry)getItem(position);
                g.DrawString(pl.Name, _this.lblArtsit.Font, pl == _this.controller.CurrentPlaying ? yellow : white, textRect, _this.alignLeft);
                g.DrawImage(playlistImg, 1, 1 + by);
                if (clicked)
                {
                    _this.controller.CurrentList = usingList;
                    _this.controller.CurrentPlaying = pl;
                    _this.controller.ImReady();
                }
            }

            public int getCount()
            {
                return usingList.Songs.Count;
            }

            public object getItem(int position)
            {

                return usingList.Songs[position];
            }

            public float getItemHeight()
            {
                return 32;
            }
        }

        private void btnFolder_Click(object sender, EventArgs e)
        {
            if (tblList.Visible)
            {
                if (currentlist is FolderAdapter)
                {
                    hideList(null);
                }
                else
                {
                    hideList(() =>
                    {
                        showList(new FolderAdapter(this));
                        position = controller.AllPlayList.IndexOf(controller.CurrentList) - 5;
                        velotry = 0.01f;
                    });
                }
            }
            else
            {
                showList(new FolderAdapter(this));
                position = controller.AllPlayList.IndexOf(controller.CurrentList) - 5;
                velotry = 0.01f;
            }
        }

        void showList(IListAdapter list)
        {
            if (!this.tblList.Visible)
            {
                tblList.Visible = true;
                isExpanding = true;
                listAnimationCountdown = 1;
            }
            currentlist = list;
            position = 0;
        }

        Action listHidedAction = null;
        void hideList(Action hidedAction)
        {
            isExpanding = false;
            listAnimationCountdown = 1;
            listHidedAction = hidedAction;
        }

        private void btnPlayList_Click(object sender, EventArgs e)
        {
            if (tblList.Visible)
            {
                if (currentlist is SongAdapter)
                {
                    hideList(null);
                }
                else
                {
                    hideList(() =>
                    {
                        actualShowSongList();

                    });
                }
            }
            else
            {
                actualShowSongList();
            }
        }

        private void actualShowSongList()
        {
            showList(new SongAdapter(this, controller.CurrentList));
            position = controller.CurrentList.Songs.IndexOf(controller.CurrentPlaying) - 5;
            velotry = 0.01f;
        }


        private void initLyrics()
        {
            lyricBackgroundBitmap = new Bitmap(Properties.Resources.bg_lyric, locLyric.Width, locLyric.Height);
            lyricComposedBitmap = new Bitmap(locLyric.Width, locLyric.Height);
            lyricComposeShadowBitmap = new Bitmap(locLyric.Width / 2, locLyric.Height / 2);
            lyricComposeTargetBitmap = new Bitmap(locLyric.Width, locLyric.Height);
            lyricCenter = new StringFormat() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center };
            lyricRect = new RectangleF(-locLyric.Width, 0, locLyric.Width * 3, locLyric.Height);
            lyricShadowBlurer = new TextShadow
            {
                Radius = 3,
                Distance = 0
            };
            if (File.Exists("lrc.ttf"))
            {
                try
                {
                    lyricPrivateFont = new PrivateFontCollection();
                    lyricPrivateFont.AddFontFile(Path.GetFullPath("lrc.ttf"));
                    Font f = new Font(lyricPrivateFont.Families[0], locLyric.Font.Size, FontStyle.Bold);
                    locLyric.Font = f;
                }
                catch (Exception)
                {
                }
            }
            lyricLayer.Visible = _showDesktopLyrics;
        }

        private LyricManager lyricManager = null;
        private PrivateFontCollection lyricPrivateFont;
        private Bitmap lyricBackgroundBitmap;
        private Bitmap lyricComposedBitmap;
        private Bitmap lyricComposeShadowBitmap;
        private Bitmap lyricComposeTargetBitmap;
        private TextShadow lyricShadowBlurer;
        readonly Brush lyricFontColorShadow = Brushes.Black;
        readonly Brush lyricFontColor = Brushes.White;

        private StringFormat lyricCenter;

        private void btnToggleCompressor_Click(object sender, EventArgs e)
        {
            if (player.DynamicRangeCompressed != 2)
            {

                player.DynamicRangeCompressed = 2;
            }
            else
            {
                player.DynamicRangeCompressed = 0;
            }
        }

        private RectangleF lyricRect;

        private void btnAdvSettings_Click(object sender, EventArgs e)
        {
            动态范围压缩ToolStripMenuItem.Checked = player.DynamicRangeCompressed == 2;
            自动增益ToolStripMenuItem.Checked = player.DynamicRangeCompressed == 1;
            显示桌面歌词ToolStripMenuItem.Checked = ShowDesktopLyrics;
            显示托盘切歌图标ToolStripMenuItem.Checked = ShowTrayIcons;
            contextMenuStrip1.Show(btnAdvSettings,Point.Empty,ToolStripDropDownDirection.AboveRight);
        }

        private void 显示桌面歌词ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ShowDesktopLyrics = !ShowDesktopLyrics;
        }

        private void 自动增益ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (player.DynamicRangeCompressed != 1)
            {

                player.DynamicRangeCompressed = 1;
            }
            else
            {
                player.DynamicRangeCompressed = 0;
            }

        }

        private void Form1_Move(object sender, EventArgs e)
        {
            layers.ForEach(l => l.updatePosition());
        }

        private LyricManager.LyricEntry currentLyricEntry = null;

        private void 显示托盘切歌图标ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ShowTrayIcons = !ShowTrayIcons;
        }

        private void 显示主窗口ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.WindowState = FormWindowState.Normal;
            this.TopMost = true;
            this.Activate();
            Application.DoEvents();
            this.TopMost = false;
        }

        private void 退出ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            btnClose_Click(sender, e);
        }

        void updateLyric()
        {
            LyricManager.LyricEntry newLyric = null;
            if (lyricManager != null)
            {
                newLyric = lyricManager.GetLyric(TimeSpan.FromMilliseconds(player.CurrentPosition));
            }
            if (newLyric != currentLyricEntry)
            {
                currentLyricEntry = newLyric;

                updateLyricCd = 32;
                inUpdateLyric = true;
            }

            lyricLayer.g.Clear(Color.Transparent);
            if (inUpdateLyric)
            {
                animatedUpdateLyric();
            }
            else
            {
                lyricAlpha -= 1f / 128f;
                if (lyricAlpha < 0f) { lyricAlpha = 0f; }
                if (lyricAlpha > 1f)
                {
                    if (currentShowingLyricBackground)
                    {
                        lyricLayer.g.DrawImage(lyricBackgroundBitmap, 0, 0);
                    }
                    lyricLayer.g.DrawImage(lyricComposeTargetBitmap, 0, 0);
                }
                else
                {
                    if (currentShowingLyricBackground)
                    {
                        DrawUtils.drawAlphaImage(lyricLayer.g, lyricBackgroundBitmap, 0, 0, locLyric.Width, locLyric.Height, lyricAlpha);
                    }

                    DrawUtils.drawAlphaImage(lyricLayer.g, lyricComposeTargetBitmap, 0, 0, locLyric.Width, locLyric.Height, lyricAlpha);
                }
            }
            lyricLayer.UpdateWindow();
        }

        private float lyricAlpha = 10;

        private int updateLyricCd = 0;
        private bool inUpdateLyric = false;

        private bool currentShowingLyricBackground = false;

        void animatedUpdateLyric()
        {
            if (updateLyricCd == 32)
            {
                preRenderLyric();
            }
            if (updateLyricCd == 16)
            {
                postRenderLyric();
            }
            float alpha = 0.0f;

            float bgAlpha = currentShowingLyricBackground ? 1.0f : 0.0f;

            if (currentShowingLyricBackground && (currentLyricEntry != null && !currentLyricEntry.isEmpty))
            {
                bgAlpha = 1.0f;
            }
            else if ((!currentShowingLyricBackground) && (currentLyricEntry == null || currentLyricEntry.isEmpty))
            {
                bgAlpha = 0.0f;
            }
            else
            {
                if (currentShowingLyricBackground)
                {
                    if (updateLyricCd >= 16)
                    {
                        bgAlpha = 1.0f;
                    }
                    if (updateLyricCd < 16)
                    {
                        bgAlpha = (updateLyricCd * 1.0f) / 16f;
                    }
                }
                else
                {
                    if (updateLyricCd >= 16)
                    {
                        bgAlpha = 0.0f;
                    }
                    if (updateLyricCd < 16)
                    {
                        bgAlpha = 1f - (updateLyricCd * 1.0f) / 16f;
                    }
                }
            }
            float postAlpha = lyricAlpha > 1.0f ? 1.0f : lyricAlpha;
            if (updateLyricCd > 16)
            {
                alpha = (float)(updateLyricCd - 16f) / 16f * postAlpha;
                if (postAlpha < 1.0f)
                {
                    bgAlpha *= postAlpha + (1f - postAlpha) * (1f - (updateLyricCd - 16f) / 16f);
                }
            }
            if (updateLyricCd < 16 && (currentLyricEntry != null && !currentLyricEntry.isEmpty))
            {
                alpha = (float)(16f - updateLyricCd) / 16f;

            }
            if (bgAlpha < alpha) { bgAlpha = alpha; }

            DrawUtils.drawAlphaImage(lyricLayer.g, lyricBackgroundBitmap, 0, 0, locLyric.Width, locLyric.Height, bgAlpha);
            DrawUtils.drawAlphaImage(lyricLayer.g, lyricComposeTargetBitmap, 0, 0, locLyric.Width, locLyric.Height, alpha);

            updateLyricCd--;
            if (updateLyricCd < 0)
            {
                lyricAlpha = currentLyricEntry == null ? 3f : currentLyricEntry.text.Length * 0.7f;
                currentShowingLyricBackground = currentLyricEntry != null && !currentLyricEntry.isEmpty;
                inUpdateLyric = false;
            }
        }

        void preRenderLyric()
        {
            using (Graphics bg = Graphics.FromImage(lyricComposeShadowBitmap))
            {
                bg.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
                bg.CompositingQuality = CompositingQuality.HighQuality;

                bg.ScaleTransform(0.5f, 0.5f);
                bg.Clear(Color.Transparent);
                using (Graphics tg = Graphics.FromImage(lyricComposedBitmap))
                {
                    tg.Clear(Color.Transparent);
                    tg.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;

                    var lyric = this.currentLyricEntry;
                    if (lyric == null || lyric.isEmpty) { return; }

                    if (lyric.hasTranslation)
                    {
                        bg.TranslateTransform(0, -14);
                        tg.TranslateTransform(0, -14);
                        bg.DrawString(lyric.translatedText, locLyric.Font, lyricFontColorShadow, lyricRect, lyricCenter);
                        tg.DrawString(lyric.translatedText, locLyric.Font, lyricFontColor, lyricRect, lyricCenter);
                        bg.TranslateTransform(0, 28);
                        tg.TranslateTransform(0, 28);
                        bg.DrawString(lyric.text, fntSub.Font, lyricFontColorShadow, lyricRect, lyricCenter);
                        tg.DrawString(lyric.text, fntSub.Font, lyricFontColor, lyricRect, lyricCenter);
                    }
                    else
                    {
                        bg.DrawString(lyric.text, locLyric.Font, lyricFontColorShadow, lyricRect, lyricCenter);
                        tg.DrawString(lyric.text, locLyric.Font, lyricFontColor, lyricRect, lyricCenter);
                    }



                }
            }

            lyricShadowBlurer.MaskShadow(lyricComposeShadowBitmap);
        }

        void postRenderLyric()
        {
            using (Graphics g = Graphics.FromImage(lyricComposeTargetBitmap))
            {
                g.Clear(Color.Transparent);
                g.InterpolationMode = InterpolationMode.HighQualityBilinear;
                if (currentLyricEntry == null || currentLyricEntry.isEmpty)
                {
                    return;
                }
                //g.DrawImage(lyricBackgroundBitmap, 0, 0, locLyric.Width, locLyric.Height);
                g.DrawImage(lyricComposeShadowBitmap, 0, 0, locLyric.Width, locLyric.Height);
                g.DrawImage(lyricComposedBitmap, 0, 0);
            }

        }







        [DllImport("user32.dll")]
        public static extern UInt32 RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, Keys vk);
        [DllImport("user32.dll")]
        public static extern bool UnregisterHotKey(IntPtr hWnd, int id);

        protected override void WndProc(ref Message m)
        {
            const int WM_HOTKEY = 0x0312;
            if (m.Msg == WM_HOTKEY)
            {
                if (m.WParam.ToInt32() == 2)
                {
                    btnPrev_Click(null, null);
                }
                if (m.WParam.ToInt32() == 3)
                {
                    btnPlay_Click(null, null);
                }
                if (m.WParam.ToInt32() == 4)
                {
                    btnNext_Click(null, null);
                }
            }

            base.WndProc(ref m);
        }


    }

    class GraphicsLayer
    {
        protected Form1 parent;
        public Rectangle relativeRect;
        public Form _this;
        public bool isActualWindow = false;

        public bool debug = false;

        public bool shouldCompose = true;

        public bool selfCompose = false;

        public Action selfComposeAction;


        public GraphicsLayer(Form1 form, Control sizeRef, bool createActualWindow)
        {
            parent = form;
            relativeRect = new Rectangle(sizeRef.Location, sizeRef.Size);
            parent.layers.Add(this);
            this.isActualWindow = createActualWindow;
            if (isActualWindow)
            {
                _this = new Form
                {
                    AutoScaleMode = AutoScaleMode.None,
                    Size = relativeRect.Size,
                    FormBorderStyle = FormBorderStyle.None,
                    ShowInTaskbar = false
                };
                _this.Load += this.OnLoad;
                updatePosition();
            }
            else
            {
                memDC = new Bitmap(relativeRect.Width, relativeRect.Height);
                g = Graphics.FromImage(memDC);
                g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
                g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
                g.CompositingQuality = CompositingQuality.HighSpeed;
                // g = parent.gdi.Graphics;
            }

        }

        public void Show(IWin32Window owner)
        {
            _this.Show(owner);
        }

        public virtual void updatePosition()
        {
            if (!isActualWindow)
            {
                return;
            }
            if (debug)
            {
                _this.Top = 0;
                _this.Left = 0;
                return;
            }
            _this.Top = parent.Top + relativeRect.Y;
            _this.Left = parent.Left + relativeRect.X;

        }

        public GdiSystem gdi;
        protected void OnLoad(object sender, EventArgs e)
        {
            gdi = new GdiSystem(_this);
            Win32.SetWindowLong(_this.Handle, Win32.GWL_EXSTYLE, Win32.GetWindowLong(_this.Handle, Win32.GWL_EXSTYLE) | Win32.WS_EX_TRANSPARENT);
            gdi.Graphics.CompositingQuality = CompositingQuality.HighSpeed;
            gdi.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighSpeed;
            gdi.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;

            g = gdi.Graphics;
        }

        public Bitmap memDC;


        internal Graphics g;

        public bool Visible
        {
            get
            {
                return _this != null && _this.Visible;
            }
            internal set
            {
                if (_this != null)
                {
                    _this.Visible = value;
                }
            }
        }

        public string Text { get; internal set; }

        public void changePosition(Point location)
        {
            if (!isActualWindow)
            {
                Point pt2 = location;
                Point offset = parent.shadowOffset;
                pt2.Offset(-offset.X, -offset.Y);
                this.relativeRect.Location = pt2;
                return;
            }
            this.relativeRect.X = location.X;
            this.relativeRect.Y = location.Y;
            this.updatePosition();
        }

        internal void UpdateWindow()
        {
            if (!isActualWindow)
            {
                return;
            }
            if (_this.InvokeRequired)
            {
                runOnUiThread(() => gdi.UpdateWindow());
                return;
            }
            gdi.UpdateWindow();
        }
        void runOnUiThread(Action a)
        {
            _this.Invoke(a);
        }




    }

    class LyricGraphicsLayer : GraphicsLayer
    {
        public LyricGraphicsLayer(Form1 form, Control sizeRef) : base(form, sizeRef, true)
        {
        }


        public override void updatePosition()
        {
            Screen s = Screen.FromControl(parent);
            Rectangle workingArea = s.WorkingArea;

            this._this.Top = workingArea.Bottom - this._this.Height;
            this._this.Left = workingArea.Left + workingArea.Width / 2 - this._this.Width / 2;
        }

    }

    class FPSCounter
    {
        private int frames = -1;
        private int lastSecond = -1;
        private int fps = 0;

        public FPSCounter() { }

        public int FPS
        {
            get
            {
                frames++;
                if (DateTime.UtcNow.Second != lastSecond)
                {
                    lastSecond = DateTime.UtcNow.Second;
                    fps = frames;
                    frames = 0;
                }

                return fps;
            }
        }
    }
}
